import 'package:code_builder/code_builder.dart';
import 'ast.dart';
import 'gen.dart';

class Lex extends Gen {
  final Map<String, Rule> _aliases = {};
  final Map<String, Option> options = {};

  Lex(Grammar grammar) : super(grammar) {
    for (var alias in grammar.optionsSection.aliases) {
      _aliases[alias.name] = Rule()..regExp = alias.regExp;
    }
  }

  Library build() {
    parseCommonOptions();

    return Library((b) {
      // Add all necessary imports.
      b.body.addAll([
        Directive.import('dart:collection'),
        Directive.import('package:string_scanner/string_scanner.dart'),
      ]);

      // Add all user-defined code up top.
      for (var dartCode in grammar.optionsSection.dartCode) {
        b.body.add(Code(dartCode.text));
      }

      // TODO: Allow defining a class
      b.body.add(buildYylex(b.body.add));
    });
  }

  Method buildYylex(void Function(Field) addField) {
    return Method((b) {
      settings.enableYystype();
      b
        ..name = settings.yylex
        ..returns = refer(settings.yystype)
        ..body = Block((b) {
          int i = 0, regexCount = 0;
          var buf = StringBuffer();
          buf.writeln('var ${settings.yytext} = yyin();');
          buf.writeln('if (${settings.yytext} == null) { return null; }');

          for (var rule in grammar.rulesSection.rules) {
            if (rule.reference != null) {
              rule = _aliases[rule.reference] ??=
                  throw "No regular expression was assigned to the name '" +
                      rule.reference +
                      "'.";
            }

            String pattern;
            if (rule.regExp != null) {
              var rgxName = pattern = '_rgx${regexCount++}';
              addField(Field((b) => b
                ..name = rgxName
                ..type = refer('RegExp')
                ..assignment = refer('RegExp')
                    .newInstance([literal(rule.regExp.pattern)]).code));
            } else {
              pattern = literal(rule.string).accept(DartEmitter()).toString();
            }

            if (i++ > 0) buf.write('else ');
            // var yyin = settings.yyin;
            // var cond =
            //     rule.eof != null ? '$yyin.isDone' : '$yyin.scan($pattern)';
            var cond = '$pattern.hasMatch(${settings.yytext})';
            var pred = rule.dartCode?.text ?? 'return ${settings.yylex}();';
            buf.writeln('''
              if ($cond) {
                $pred
              }
              ''');
          }

          if (grammar.rulesSection.rules.isNotEmpty) buf.writeln(' else {');

          buf.writeln('yyout(${settings.yytext});');
          // buf.writeln('''
          //   throw FormatException(
          //     'syntax error\\n\${${settings.yyin}.emptySpan.highlight()}'
          //   );
          //   ''');

          if (grammar.rulesSection.rules.isNotEmpty) buf.writeln('}');
          b.statements.add(Code(buf.toString()));

          // Finally, add any free text.
          b.statements.add(Code(grammar.freeTextSection.text));
        });
    });
  }
}
